package com.xuecheng.media.domain.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.xuecheng.base.exception.XueChengPlusException;
import com.xuecheng.base.model.PageResult;
import com.xuecheng.media.domain.event.MediaUploadRecordEvent;
import com.xuecheng.media.domain.mapper.MediaFilesMapper;
import com.xuecheng.media.domain.mapper.MediaProcessMapper;
import com.xuecheng.media.domain.model.ChunkMergeDo;
import com.xuecheng.media.domain.model.ChunkUploadDo;
import com.xuecheng.media.domain.model.FileUploadDo;
import com.xuecheng.media.domain.model.entity.MediaProcess;
import com.xuecheng.media.domain.model.enums.FileTypeEnum;
import com.xuecheng.media.domain.service.MediaFilesService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xuecheng.media.infrastructure.enums.MediaExtensionEnum;
import com.xuecheng.media.infrastructure.enums.MimeTypeEnum;
import com.xuecheng.media.infrastructure.enums.UploadStatusEnum;
import com.xuecheng.media.infrastructure.util.MinioUtil;
import com.xuecheng.media.infrastructure.util.UploadUtil;
import com.xuecheng.media.infrastructure.util.VideoUtil;
import io.minio.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import com.xuecheng.media.domain.model.entity.MediaFiles;
import org.springframework.transaction.annotation.Transactional;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;


/**
 * <p>
 * 媒资信息 服务实现类
 * </p>
 *
 * @author itcast
 */
@Slf4j
@Service
public class MediaFilesServiceImpl extends ServiceImpl<MediaFilesMapper, MediaFiles> implements MediaFilesService {

    @Autowired
    private MediaFilesMapper filesMapper;

    // 普通文件桶
    @Value("${minio.bucket.files}")
    private String bucketFiles;

    // 存储视频
    @Value("${minio.bucket.videofiles}")
    private String bucketVideo;

    @Autowired
    private MinioClient minioClient;

    @Autowired
    private MinioUtil minioUtil;

    @Autowired
    private MediaProcessMapper mediaProcessMapper;

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public MediaFiles uploadFile(FileUploadDo fileUploadDo, String absolutePath, Long companyId, String objectName) {
        File file = new File(absolutePath);
        if (!file.exists()){
            throw new XueChengPlusException("临时文件不存在");
        }
        String fileMd5 = UploadUtil.getFileMd5(file);
        String defaultAbsolutePath = objectName;
        String extension;
        String filePath;
        // 如果传入固定的参数就用固定的参数
        if (Objects.isNull(objectName)){
            defaultAbsolutePath = UploadUtil.getDefaultAbsolutePath();
            extension = UploadUtil.getExtension(fileUploadDo.getFilename());
            filePath = defaultAbsolutePath + fileMd5 + "." + extension;
        } else {
            extension = UploadUtil.getExtension(objectName);
            filePath = objectName;
            fileUploadDo.setFilename(UploadUtil.getFileName(objectName));
        }
        String mimeType = UploadUtil.getMimeType(extension);

        // 如果是图片需要设置格式
        if (MediaExtensionEnum.judgeImage(extension)){
            fileUploadDo.setTags(FileTypeEnum.IMAGE.getType());
            fileUploadDo.setFileType(FileTypeEnum.IMAGE.getCode());
        }

        // 保存minio
        ((MediaFilesService)(AopContext.currentProxy())).addMediaFileToMinio(absolutePath, mimeType, bucketFiles,
                filePath);
        // 保存数据库
        fileUploadDo.setFileSize(file.length());
        MediaFiles mediaFiles = ((MediaFilesService) AopContext.currentProxy()).addMediaFileToDb(filePath, fileUploadDo, fileMd5,
                bucketFiles, companyId);

        return mediaFiles;
    }


    @Override
    @Transactional
    public MediaFiles addMediaFileToDb(String filePath, FileUploadDo fileUploadDo, String fileMd5, String bucketFiles, Long companyId) {
        MediaFiles mediaFiles = filesMapper.selectById(fileMd5);
        if (Objects.isNull(mediaFiles)){
            MediaFiles saveMediaFile = MediaFiles.getDefaultMediaFile(filePath, fileUploadDo, fileMd5, companyId, bucketFiles);
            filesMapper.insert(saveMediaFile);
            // 保存到待处理的表
            saveWaitHandleTask(saveMediaFile);
            return saveMediaFile;
        }

        return mediaFiles;
    }

    /**
     * 保存待处理任务
     * @param mediaFiles
     */
    private void saveWaitHandleTask(MediaFiles mediaFiles) {
        String filename = mediaFiles.getFilename();
        String mimeType = UploadUtil.getMimeType(filename.substring(filename.lastIndexOf(".")));
        if (mimeType.equals(MimeTypeEnum.AVI.getType())){
            MediaProcess mediaProcess = MediaProcess.buildByMediaFiles(mediaFiles);
            mediaProcessMapper.insert(mediaProcess);
        }
    }

    @Override
    public void addMediaFileToMinio(String absolutePath, String mimeType, String bucketFiles, String filePath) {
       try {
           UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder()
                   .filename(absolutePath) //指定本地文件路径
                   .object(filePath) //对象名，放在子目录下
                   .contentType(mimeType) // 文件类型
                   .bucket(bucketFiles)
                   .build();
           minioClient.uploadObject(uploadObjectArgs);
       } catch (Exception e){
           log.error("上传minio出错，原因：{}", e.getMessage());
           throw new XueChengPlusException("上传minio出错");
       }
    }

    @Override
    public Boolean checkMinioFileExit(MediaFiles mediaFiles) {
        // 数据库中存在就查找minio中是否存在
        try {
            InputStream object = minioClient.getObject(
                    GetObjectArgs.builder()
                            .bucket(mediaFiles.getBucket())
                            .object(mediaFiles.getFilePath())
                            .build());
            if (Objects.nonNull(object)){
                object.close();
                // 文件已经存在
                return true;
            }
        } catch (Exception e){
            throw new XueChengPlusException("检查文件出错");
        }
        return false;
    }

    @Override
    public Boolean checkChunkExit(String fileMd5, Integer chunk) {
        // 获取分块文件目录
        String chunkObjPath = UploadUtil.getChunkObjPath(fileMd5, chunk);
        try {
            InputStream object = minioUtil.getObjStream(bucketVideo, chunkObjPath);
            if (Objects.nonNull(object)){
                object.close();
                // 分块已经存在
                return true;
            }
        } catch (Exception e){
            log.debug("检查分块出错，原因：{}", e.getMessage());
        }
        return false;
    }

    @Override
    public void uploadChunk(ChunkUploadDo chunkUploadDo) {
        String chunkObjPath = UploadUtil.getChunkObjPath(chunkUploadDo.getFileMd5(), chunkUploadDo.getChunk());
        String mimeType = UploadUtil.getMimeType(null);
        addMediaFileToMinio(chunkObjPath, mimeType, bucketVideo, chunkUploadDo.getAbsolutePath());
        // 修改上传状态为上传中
        applicationEventPublisher.publishEvent(new MediaUploadRecordEvent(this, chunkUploadDo.getFileMd5(),
                UploadStatusEnum.UPLOADING.getType()));
    }

    @Override
    public void mergeChunks(ChunkMergeDo chunkMergeDo) {
        String chunkPath = UploadUtil.getChunkPath(chunkMergeDo.getFileMd5());
        // 分片目录下的分片列表
        List<ComposeSource> composeSourceList = new ArrayList<>();
        for(int i = 0; i < chunkMergeDo.getChunkTotal(); i++){
            composeSourceList.add(minioUtil.getComposeSource(bucketVideo, chunkPath + i));
        }
        // 合并分片
        String defaultAbsolutePath = UploadUtil.getDefaultAbsolutePath();
        String extension = UploadUtil.getExtension(chunkMergeDo.getFilName());
        String filePath = defaultAbsolutePath + chunkMergeDo.getFileMd5() + "." + extension;
        minioUtil.mergeComposeSource(bucketVideo, filePath, composeSourceList);
        // 验证分片是否正确
        File resFile = null;
        try {
            resFile = minioUtil.getObj(bucketVideo, filePath);
            if (Objects.isNull(resFile)){
                throw new XueChengPlusException("获取对象流出错");
            }
            InputStream inputStream = new FileInputStream(resFile);
            if (!DigestUtils.md5Hex(inputStream).equals(chunkMergeDo.getFileMd5())){
                throw new XueChengPlusException("文件校验出错");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 保存数据库
        FileUploadDo fileUploadDo = FileUploadDo.getDefaultFile(resFile.length(), chunkMergeDo.getFilName());
        fileUploadDo.setTags(FileTypeEnum.VIDEO.getType());
        fileUploadDo.setFileType(FileTypeEnum.VIDEO.getCode());
        addMediaFileToDb(filePath, fileUploadDo, chunkMergeDo.getFileMd5(), bucketVideo, chunkMergeDo.getCompanyId());
        // 清理分片文件
        minioUtil.clearChunkPath(chunkPath, chunkMergeDo.getChunkTotal(), bucketVideo);
        // 修改上传状态为上传完成
        applicationEventPublisher.publishEvent(new MediaUploadRecordEvent(this, chunkMergeDo.getFileMd5(),
                UploadStatusEnum.OVER.getType()));
    }

    @Override
    public PageResult<MediaFiles> queryCourseBasePage(LambdaQueryWrapper<MediaFiles> queryWrapper, Page<MediaFiles> page) {
        Page<MediaFiles> pageResult = filesMapper.selectPage(page, queryWrapper);
        // 获取数据列表
        List<MediaFiles> list = pageResult.getRecords();
        // 获取数据总数
        long total = pageResult.getTotal();
        // 构建结果集
        return new PageResult<>(list, total, page.getCurrent(), page.getSize());
    }

    @Override
    public void updateMediaFiles(MediaFiles mediaFiles) {
        MediaFiles media = filesMapper.selectById(mediaFiles.getId());
        BeanUtils.copyProperties(mediaFiles, media);
        filesMapper.updateById(media);
    }
}
